#!/usr/bin/env python
# coding: utf-8

# # 深度学习框架使用
# > 该notebook通过房价数据集学习深度学习框架使用 <br>
# > 根据房子属性和历史成交价预测给定属性的房子价值
# 
# ### pipeline
# - 前处理
# - 特征
# - 模型
# - 后处理
# - 本地验证
# 
# ### 相比GBDT模型（XGBoost, LightGBM, CatBoost）
# - nn需要处理NA
# - nn需要特征标准化norm
# 
# 
# ### 回归任务常用技巧
# - 回归任务标签转换
# - 特征
# - 模型
# - 后处理
# - 模型融合

# ## 导入包 Load package

# In[4]:


import os
import warnings
import numpy as np
import pandas as pd
from scipy.stats import skew, pearsonr
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error
from sklearn.preprocessing import LabelEncoder, OneHotEncoder

import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader

warnings.filterwarnings("ignore")
# get_ipython().run_line_magic('matplotlib', 'inline')


# ## 配置超参数 Setup configuration

# In[5]:


class CFG:
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    epochs = 200
    learning_rate = 0.002
    batch_size = 256


# In[6]:


import random


# 准备工作 experiment prepare

def seed_everything(seed):
    """
    Seeds basic parameters for reproductibility of results.

    Args:
        seed (int): Number of the seed.
    """
    random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False


# ## 准备数据 Load data

# In[7]:


train = pd.read_csv('./house-prices-advanced-regression-techniques/train.csv')
test = pd.read_csv('./house-prices-advanced-regression-techniques/test.csv')
submission = pd.read_csv('./house-prices-advanced-regression-techniques/sample_submission.csv')


# In[8]:


# check data
train


# In[9]:


# preprocess
numerical_col = []
categorical_col = []

types_dict = train.columns.to_series().groupby(train.dtypes).groups

for key, vol in types_dict.items():
    if key == 'int' or key == 'float':
        for col in range(len(vol)):
            if vol[col] != 'SalePrice':
                numerical_col.append(vol[col])
    else:
        for col in range(len(vol)):
            categorical_col.append(vol[col])

print(train[numerical_col].isna().sum())


# In[10]:


# EDA-feature

plt.subplots(8, 4, figsize=(30, 30))

# Plot a density plot for each variable
for idx, col in enumerate(numerical_col[:32]):
    ax = plt.subplot(8, 4, idx + 1)
    ax.yaxis.set_ticklabels([])
    sns.distplot(train[col], hist=False, axlabel=False,
                 kde_kws={'linestyle': '-', 'color': 'blue', 'label': "No Diabetes"})
    ax.set_title(col)

plt.show()


# In[11]:


# EDA-label
plt.subplot(121)
plt.hist(train['SalePrice'], bins=100);
plt.subplot(122)
plt.hist(np.log1p(train['SalePrice']), bins=100);


# In[12]:


# norm
scaler = StandardScaler()

X_num = train[numerical_col]
X_num = X_num.fillna(0)
X_num = scaler.fit_transform(X_num)

one_hot = OneHotEncoder(handle_unknown='ignore')
X_cat = one_hot.fit_transform(train[categorical_col]).toarray()
X = np.concatenate([X_num, X_cat], axis=1)

y = train['SalePrice'].values
y = np.log1p(y)


# In[13]:


# dataset

class HouseDataset(Dataset):
    def __init__(self, X, y):
        self.X = X
        self.y = y

    def __len__(self):
        return len(self.y)

    def __getitem__(self, idx):
        feature = self.X[idx]
        label = self.y[idx]
        return torch.tensor(feature, dtype=torch.float32), torch.tensor(label, dtype=torch.float32)


# In[14]:


# check dataset
dataset = HouseDataset(X, y)
dataset[0]


# ## 搭建模型 Build model

# In[23]:


class HouseModel(nn.Module):
    def __init__(self, input_dim):
        super().__init__()
        self.mlp = nn.Sequential(
            nn.Linear(input_dim, 512),
            nn.ReLU(),
            nn.Linear(512, 256),
            nn.ReLU(),
            nn.Linear(256, 1),
            # nn.Sigmoid()
        )

    def forward(self, x):
        x = self.mlp(x)
        return x


# In[24]:


# 测试模型 test model
model = HouseModel(input_dim=19)
print(model)

x_random = torch.randn(2, 19)  # (batch_size, 特征数量)
model = HouseModel(19)
y_random = model(x_random)
print(y_random.shape)
assert y_random.shape == (2, 1)


# ## 训练和验证 Train and Validate

# In[25]:


X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=0)

train_dataset = HouseDataset(X_train, y_train)
valid_dataset = HouseDataset(X_valid, y_valid)
train_loader = DataLoader(train_dataset, batch_size=CFG.batch_size, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=CFG.batch_size, shuffle=False)

print(X_train.shape, y_train.shape, X_valid.shape, y_valid.shape)
print(train_dataset[0][0].shape, train_dataset[0][1].shape)

for feature, label in valid_loader:
    print(feature.size(), label.size())
    break


# In[26]:


model = HouseModel(X.shape[1])
model.to(CFG.device)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=CFG.learning_rate)

for n in range(CFG.epochs):
    # train
    model.train()
    for feature, label in train_loader:
        y_pred = model(feature.to(CFG.device))
        loss = criterion(y_pred, label.to(CFG.device))
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    # valid
    y_preds = []
    model.eval()
    with torch.no_grad():
        for valid_feature, valid_label in valid_loader:
            y_pred = model(valid_feature.to(CFG.device)).squeeze(-1)
            y_preds.append(y_pred.detach().cpu().numpy())

    y_preds = np.concatenate(y_preds, 0)
    mse = np.sqrt(mean_squared_error(y_valid, y_preds))
    print(f"Epoch: {n + 1}, Train Loss: {loss.item()}, Valid Score: {mse}")

# save the model
torch.save(model, 'my_model.pth')


# In[27]:


# training process visual
print(X.shape)
print(y.shape)


# ## 检查结果 Check the result

# In[28]:


plt.plot(y_valid.reshape(-1), label='truth')
plt.plot(y_preds.reshape(-1), label='prediction')
plt.legend()


# ## 测试

# In[29]:


df_test = pd.read_csv(r'./house-prices-advanced-regression-techniques/test.csv')
submission_id = df_test["Id"].copy()

df_test


# In[30]:


df_test['SalePrice'] = 0
df_test[numerical_col] = df_test[numerical_col].fillna(0)
x_test_num = scaler.fit_transform(df_test[numerical_col])
x_test_cat = one_hot.transform(df_test[categorical_col]).toarray()
x_test = np.concatenate([x_test_num, x_test_cat], axis=1)


x_test = torch.tensor(x_test).float().to(CFG.device)

# model = HouseModel(input_dim=19)
model=torch.load('my_model.pth') # 导入网络的参数



y_submission = model(x_test).squeeze()  # Make it into array
y_submission = torch.exp(y_submission)  # Scale back to normal value
y_submission = y_submission.cpu().detach().numpy()  # Change to numpy
submission = pd.DataFrame({"Id": submission_id, "SalePrice": y_submission})  # Make dataframe for submissino
submission.to_csv('submission.csv', index=False)  # Put into csv file

submission


# ## 改进建议
# - 如何增加更多特征？
# - 如何进行模型融合？
# - 可以尝试使用更深MLP网络
# - 可以使用传统机器学习方法
# - 可以使用TTA

# In[ ]:




